import { _decorator, Component, Node, UITransform, Graphics, Color, Widget, Layers, Vec3, Vec2 } from 'cc';
import { CHUNK_SIZE, ChunkManager } from '../world/ChunkManager';
import ExtractSpawner from '../gameplay/ExtractSpawner';

const { ccclass, property } = _decorator;

const DRAW_INTERVAL = 0.2;
const CHUNK_DENOMINATOR = CHUNK_SIZE * 2;

function clamp01(v: number): number {
  if (v < 0) return 0;
  if (v > 1) return 1;
  return v;
}

@ccclass('MiniMap')
export class MiniMap extends Component {
  @property
  public size = 128;

  private chunkManager: ChunkManager | null = null;
  private extractSpawner: ExtractSpawner | null = null;
  private player: Node | null = null;

  private readonly tmpVec2 = new Vec2();
  private readonly tmpVec3 = new Vec3();

  public startLoop() {
    this.unschedule(this.redraw);
    this.schedule(this.redraw, DRAW_INTERVAL);
  }

  public stopLoop() {
    this.unschedule(this.redraw);
  }

  public setDependencies(player: Node, chunkManager: ChunkManager, extractSpawner: ExtractSpawner) {
    this.player = player;
    this.chunkManager = chunkManager;
    this.extractSpawner = extractSpawner;
    this.redraw();
  }

  private redraw = () => {
    try {
      const g = this.node.getComponent(Graphics);
      if (!g || !this.player || !this.chunkManager || !this.extractSpawner) {
        return;
      }

      g.clear();
      g.fillColor = new Color(10, 12, 16, 196);
      g.rect(0, 0, this.size, this.size);
      g.fill();
      g.strokeColor = new Color(70, 70, 70, 255);
      g.rect(0, 0, this.size, this.size);
      g.stroke();

      const playerPos = this.player.worldPosition;
      const chunks = this.chunkManager.getLoadedChunks();
      const extracts = this.extractSpawner.getRevealedExtracts();

      // Draw chunks
      g.strokeColor = new Color(110, 110, 110, 255);
      const chunkWidth = (CHUNK_SIZE / CHUNK_DENOMINATOR) * this.size;
      const chunkHalf = chunkWidth * 0.5;
      for (const chunk of chunks) {
        const center = this.tmpVec3.set(
          chunk.cx * CHUNK_SIZE + CHUNK_SIZE * 0.5,
          chunk.cy * CHUNK_SIZE + CHUNK_SIZE * 0.5,
          0,
        );
        const miniCenter = this.worldToMini(center, this.tmpVec2);
        g.rect(miniCenter.x - chunkHalf, miniCenter.y - chunkHalf, chunkWidth, chunkWidth);
        g.stroke();
      }

      // Draw extracts
      for (const ex of extracts) {
        const mini = this.worldToMini(ex.pos, this.tmpVec2);
        g.fillColor = ex.kind === 'hidden' ? new Color(255, 210, 40, 255) : new Color(0, 200, 0, 255);
        g.circle(mini.x, mini.y, 4);
        g.fill();
      }

      // Draw player
      const playerMini = this.worldToMini(playerPos, this.tmpVec2);
      g.fillColor = new Color(255, 255, 255, 255);
      g.circle(playerMini.x, playerMini.y, 3);
      g.fill();

      if (typeof console !== 'undefined' && console.debug) {
        console.debug(
          `[MM] redraw chunks=${chunks.length} extracts=${extracts.length} player(${playerPos.x.toFixed(1)},${playerPos.y.toFixed(1)})`,
        );
      }
    } catch (err) {
      if (typeof console !== 'undefined' && console.error) {
        console.error('[BOOT-ERROR]', err);
      }
      throw err;
    }
  };

  private worldToMini(world: Vec3, out: Vec2): Vec2 {
    const camPos = this.player ? this.player.worldPosition : Vec3.ZERO;
    const normX = clamp01((world.x - camPos.x) / CHUNK_DENOMINATOR + 0.5);
    const normY = clamp01((world.y - camPos.y) / CHUNK_DENOMINATOR + 0.5);
    out.x = normX * this.size;
    // flip Y so world up appears upward in minimap
    out.y = (1 - normY) * this.size;
    return out;
  }
}

export default MiniMap;

interface MiniMapBootstrapOptions {
  player: Node | null;
  chunkManager: ChunkManager;
  extractSpawner: ExtractSpawner;
}

export function bootstrapMiniMap(map: MiniMap, options: MiniMapBootstrapOptions) {
  console.log('[MM] bootstrap...');
  map.node.layer = Layers.Enum.UI_2D;
  const ui = map.node.getComponent(UITransform) ?? map.node.addComponent(UITransform);
  ui.setContentSize(map.size, map.size);
  const widget = map.node.getComponent(Widget) ?? map.node.addComponent(Widget);
  widget.isAlignTop = true;
  widget.isAlignRight = true;
  widget.top = 8;
  widget.right = 8;
  map.node.getComponent(Graphics) ?? map.node.addComponent(Graphics);
  if (options.player) {
    map.setDependencies(options.player, options.chunkManager, options.extractSpawner);
  }
  map.startLoop();
}
